/* 
 * Copyright 2010 Ziya Guven Koylu, Ozge Koylu
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.koylu.caffein;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.koylu.caffein.converter.Converter;
import com.koylu.caffein.model.caffein.CaffeinConfig;
import com.koylu.caffein.model.clazz.Clazz;
import com.koylu.caffein.model.clazz.Column;
import com.koylu.caffein.model.clazz.ForeignKey;
import com.koylu.caffein.model.clazz.ManyToMany;
import com.koylu.caffein.model.clazz.ManyToOne;
import com.koylu.caffein.model.clazz.id.AutoIncrementId;
import com.koylu.caffein.model.clazz.id.CompositeId;
import com.koylu.caffein.model.clazz.id.SequenceId;
import com.koylu.caffein.model.container.ClazzContainer;

public class ObjectToQuery {

	private static Log log = LogFactory.getLog(ObjectToQuery.class);
	private Map<String, StringBuffer> queryMap = new HashMap<String, StringBuffer>();

	public PreparedStatement load(CaffeinConfig caffeinConfig, Connection connection, Clazz clazz, Object id) throws Exception {
		StringBuffer query = queryMap.get("LOAD_" + clazz.getName());
		if (query == null) {
			query = new StringBuffer("SELECT ");
			for (Iterator<Column> iterator = clazz.getAllColumnMap().values().iterator(); iterator.hasNext();) {
				Column column = iterator.next();
				query.append(column.getColumn()).append(" ").append(column.getColumnAlias());
				if (iterator.hasNext()) {
					query.append(", ");
				}
			}
			query.append(" FROM ").append(clazz.getTable()).append(" WHERE ");
			for (Iterator<String> iterator = clazz.getIdColumnMap().keySet().iterator(); iterator.hasNext();) {
				query.append(iterator.next()).append("=?");
				if (iterator.hasNext()) {
					query.append(" AND ");
				}
			}
			queryMap.put("LOAD_" + clazz.getName(), query);
		}
		String queryString = query.toString();
		if(caffeinConfig.isEnableQueryLogging()){
			log.info(queryString);
		}
		PreparedStatement preparedStatement = null;
		try {
			preparedStatement = connection.prepareStatement(queryString);
			int k = 1;
			for (Column column : clazz.getIdColumnMap().values()) {
				String pathForId = column.getPathForId(caffeinConfig);
				Object value = id;
				if(!"".equals(pathForId)){
					value = PropertyUtils.getNestedProperty(id, pathForId);				
				}
				column.getConverter(caffeinConfig).fieldToColumn(preparedStatement, value, column.isColumnNullable(caffeinConfig), k++);
			}			
		} catch (Exception e) {
			if(preparedStatement != null){
				try {
					preparedStatement.close();					
				} catch (Exception e2) {}
			}
			throw e;
		}
		return preparedStatement;
	}

	public PreparedStatement save(CaffeinConfig caffeinConfig, Connection connection, Clazz clazz, Object object) throws Exception {
		StringBuffer query = queryMap.get("INSERT_" + clazz.getName());
		Set<String> columns;
		if (clazz.getId() instanceof AutoIncrementId) {
			columns = clazz.getColumnMap().keySet();
		} else {
			columns = clazz.getAllColumnMap().keySet();
		}
		if (query == null) {
			query = new StringBuffer("INSERT INTO ").append(clazz.getTable());
			StringBuffer left = new StringBuffer("(");
			StringBuffer right = new StringBuffer("(");
			for (Iterator<String> iterator = columns.iterator(); iterator.hasNext();) {
				String column = iterator.next();
				if (clazz.getAllColumnMap().get(column) instanceof SequenceId) {
					left.append(column);
					right.append(((SequenceId) clazz.getAllColumnMap().get(column)).getSequence()).append(".").append(SequenceId.NEXTVAL);
				} else {
					left.append(column);
					right.append("?");
				}
				if (iterator.hasNext()) {
					left.append(",");
					right.append(",");
				}
			}
			left.append(")");
			right.append(")");
			query.append(left).append(" VALUES ").append(right);
			queryMap.put("INSERT_" + clazz.getName(), query);
		}
		String queryString = query.toString();
		if(caffeinConfig.isEnableQueryLogging()){
			log.info(queryString);
		}
		PreparedStatement preparedStatement = null;
		try {
			String[] generatedColumnNames = clazz.getId().getGeneratedColumnNames();
			if(caffeinConfig.supportsGetGeneratedKeys() && generatedColumnNames != null){
				preparedStatement = connection.prepareStatement(queryString, generatedColumnNames);
			}else{
				preparedStatement = connection.prepareStatement(queryString);
			}
			int k = 1;
			for (String columnName : columns) {
				Column column = clazz.getAllColumnMap().get(columnName);
				if (!(column instanceof SequenceId)) {
					Object value = null;
					String path = column.getPath(caffeinConfig);
					try {
						value = PropertyUtils.getNestedProperty(object, path);
					} catch (Exception e) {}
					column.getConverter(caffeinConfig).fieldToColumn(preparedStatement, value, column.isColumnNullable(caffeinConfig), k++);
				}
			}	
		} catch (Exception e) {
			if(preparedStatement != null){
				try {
					preparedStatement.close();					
				} catch (Exception e2) {}
			}
			throw e;
		}
		return preparedStatement;
	}

	public PreparedStatement update(CaffeinConfig caffeinConfig, Connection connection, Clazz clazz, Object object) throws Exception {
		StringBuffer query = queryMap.get("UPDATE_" + clazz.getName());
		if (query == null) {
			query = new StringBuffer("UPDATE ").append(clazz.getTable()).append(" SET ");
			for (Iterator<String> iterator = clazz.getColumnMap().keySet().iterator(); iterator.hasNext();) {
				query.append(iterator.next()).append("=?");
				if (iterator.hasNext()) {
					query.append(",");
				}
			}
			query.append(" WHERE ");
			for (Iterator<String> iterator = clazz.getIdColumnMap().keySet().iterator(); iterator.hasNext();) {
				query.append(iterator.next()).append("=?");
				if (iterator.hasNext()) {
					query.append(" AND ");
				}
			}
			queryMap.put("UPDATE_" + clazz.getName(), query);
		}
		String queryString = query.toString();
		if(caffeinConfig.isEnableQueryLogging()){
			log.info(queryString);
		}
		PreparedStatement preparedStatement = null;
		try {
			preparedStatement = connection.prepareStatement(queryString);
			int k = 1;
			for (String columnName : clazz.getColumnMap().keySet()) {
				Column column = clazz.getAllColumnMap().get(columnName);
				Object value = null;
				String path = column.getPath(caffeinConfig);
				try {
					value = PropertyUtils.getNestedProperty(object, path);
				} catch (Exception e) {}
				column.getConverter(caffeinConfig).fieldToColumn(preparedStatement, value, column.isColumnNullable(caffeinConfig), k++);
			}
			Object id = ((ClazzContainer) object).getContainerId();
			for (String columnName : clazz.getIdColumnMap().keySet()) {
				Column column = clazz.getAllColumnMap().get(columnName);
				String pathForId = column.getPathForId(caffeinConfig);
				Object value = id;
				if(!"".equals(pathForId)){
					value = PropertyUtils.getNestedProperty(id, pathForId);				
				}
				column.getConverter(caffeinConfig).fieldToColumn(preparedStatement, value, column.isColumnNullable(caffeinConfig), k++);
			}	
		} catch (Exception e) {
			if(preparedStatement != null){
				try {
					preparedStatement.close();					
				} catch (Exception e2) {}
			}
			throw e;
		}
		return preparedStatement;
	}

	public PreparedStatement delete(CaffeinConfig caffeinConfig, Connection connection, Clazz clazz, Object id) throws Exception {
		StringBuffer query = queryMap.get("DELETE_" + clazz.getName());
		if (query == null) {
			query = new StringBuffer("DELETE FROM ").append(clazz.getTable()).append(" WHERE ");
			for (Iterator<String> iterator = clazz.getIdColumnMap().keySet().iterator(); iterator.hasNext();) {
				query.append(iterator.next()).append("=?");
				if (iterator.hasNext()) {
					query.append(" AND ");
				}
			}
			queryMap.put("DELETE_" + clazz.getName(), query);
		}
		String queryString = query.toString();
		if(caffeinConfig.isEnableQueryLogging()){
			log.info(queryString);
		}
		PreparedStatement preparedStatement = null;
		try {
			preparedStatement = connection.prepareStatement(queryString);
			int k = 1;
			for (String columnName : clazz.getIdColumnMap().keySet()) {
				Column column = clazz.getIdColumnMap().get(columnName);
				String pathForId = column.getPathForId(caffeinConfig);
				Object value = id;
				if(!"".equals(pathForId)){
					value = PropertyUtils.getNestedProperty(id, pathForId);				
				}
				column.getConverter(caffeinConfig).fieldToColumn(preparedStatement, value, column.isColumnNullable(caffeinConfig), k++);
			}			
		} catch (Exception e) {
			if(preparedStatement != null){
				try {
					preparedStatement.close();					
				} catch (Exception e2) {}
			}
			throw e;
		}
		return preparedStatement;
	}
	
	public PreparedStatement deleteManyToMany(CaffeinConfig caffeinConfig, ManyToMany manyToMany, Connection connection, Clazz clazz, Object object) throws Exception {
		StringBuffer query = queryMap.get("DELETE_MANY_TO_MANY_" + clazz.getName());
		CompositeId id = (CompositeId)clazz.getId();
		ManyToOne manyToOne = (ManyToOne)id.getChild("local");
		if(query == null){
			query = new StringBuffer("DELETE FROM ").append(clazz.getTable()).append(" WHERE ");
			for (Iterator<ForeignKey> iterator = manyToOne.getChildren().iterator(); iterator.hasNext();) {
				ForeignKey foreignKey = iterator.next();
				query.append(foreignKey.getColumn()).append("=?");
				if (iterator.hasNext()) {
					query.append(" AND ");
				}
			}
			queryMap.put("DELETE_MANY_TO_MANY_" + clazz.getName(), query);
		}
		String queryString = query.toString();
		if(caffeinConfig.isEnableQueryLogging()){
			log.info(queryString);
		}
		PreparedStatement preparedStatement = null;
		try {
			preparedStatement = connection.prepareStatement(queryString);
			Object compositeObjectId = id.getRealClass(caffeinConfig).newInstance();
			PropertyUtils.setSimpleProperty(compositeObjectId, "local", object);
			int k = 1;
			for (Iterator<ForeignKey> iterator = manyToOne.getChildren().iterator(); iterator.hasNext();) {
				ForeignKey foreignKey = iterator.next();
				String pathForId = foreignKey.getPathForId(caffeinConfig);
				Object value = PropertyUtils.getNestedProperty(compositeObjectId, pathForId);
				foreignKey.getConverter(caffeinConfig).fieldToColumn(preparedStatement, value, foreignKey.isColumnNullable(caffeinConfig), k++);
			}			
		} catch (Exception e) {
			if(preparedStatement != null){
				try {
					preparedStatement.close();					
				} catch (Exception e2) {}
			}
			throw e;
		}
		return preparedStatement;
	}
	
	public PreparedStatement updateOneToMany(CaffeinConfig caffeinConfig, ManyToOne manyToOne, Connection connection, Clazz clazz, Object object) throws Exception {
		StringBuffer query = queryMap.get("UPDATE_ONE_TO_MANY_" + clazz.getName());
		if(query == null){
			query = new StringBuffer("UPDATE ").append(clazz.getTable()).append(" SET ");
			StringBuffer right = new StringBuffer(" WHERE ");
			for (Iterator<ForeignKey> iterator = manyToOne.getChildren().iterator(); iterator.hasNext();) {
				ForeignKey foreignKey = iterator.next();
				query.append(foreignKey.getColumn()).append("=?");
				right.append(foreignKey.getColumn()).append("=?");
				if (iterator.hasNext()) {
					query.append(", ");
					right.append(" AND ");
				}
			}
			query.append(right);
			queryMap.put("UPDATE_ONE_TO_MANY_" + clazz.getName(), query);
		}
		String queryString = query.toString();
		if(caffeinConfig.isEnableQueryLogging()){
			log.info(queryString);
		}
		PreparedStatement preparedStatement = null;
		try {
			preparedStatement = connection.prepareStatement(queryString);
			int k=1;
			int manyToOneChildSize = manyToOne.getChildren().size();
			for (Iterator<ForeignKey> iterator = manyToOne.getChildren().iterator(); iterator.hasNext();) {
				ForeignKey foreignKey = iterator.next();
				String path = foreignKey.getRealNode(caffeinConfig).getPath(caffeinConfig);
				Converter converter = foreignKey.getConverter(caffeinConfig); 
				converter.fieldToColumn(preparedStatement, null, true, k++);
				Object value = PropertyUtils.getNestedProperty(object, path);
				int j = k + manyToOneChildSize - 1;
				converter.fieldToColumn(preparedStatement, value, true, j );
			}			
		} catch (Exception e) {
			if(preparedStatement != null){
				try {
					preparedStatement.close();					
				} catch (Exception e2) {}
			}
			throw e;
		}
		return preparedStatement;
	}
}